﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Dycox.Collections;

namespace Dycox.Workflows
{
    public class ThreadLock
    {
        public ThreadLock(string name, string description)
        {
            if (string.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");

            _name = name;
            _description = description;
        }

        private string _name;

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        private string _description;

        public string Description
        {
            get { return _description; }
            set { _description = value; }
        }

        private Workflow _workflow;

        public Workflow Workflow
        {
            get { return _workflow; }
            internal set { _workflow = value; }
        }

        private void ValidateProcess(WFProcess process)
        {
            if (process == null)
                throw new ArgumentNullException("process");

            if (_workflow == null)
                throw new InvalidOperationException("Current thread lock is not available for operation.");

            if (process.WorkflowName != _workflow.Name)
                throw new WorkflowException("The specified process does not belong the current workflow.");
        }

        internal int Set(WFStep step)
        {
            WFProcess process = step.Thread.Process;

            ValidateProcess(process);

            LockThread(step);

            return process.Properties.GetThreadLockData(_name).Increase();
        }

        internal int Release(WFStep step)
        {
            WFProcess process;

            process = step.Thread.Process;
            ValidateProcess(process);

            UnlockThread(step);

            return process.Properties.GetThreadLockData(_name).Decrease();
        }

        private int GetCounter(WFProcess process)
        {
            return process.Properties.GetThreadLockData(_name).Counter;
        }

        internal bool ShouldWait(WFProcess process)
        {
            return GetCounter(process) > 0;
        }

        private void LockThread(WFStep step)
        {
            WFThread thread = step.Thread;
            var properties = thread.Properties;

            if (properties.IsSynchronizing)
                throw new WorkflowException("Cannot lock thread because it was locked already.");

            properties.SuspendPropertyChanged();
            EnterSynchronizing(step, properties);
            properties.IsLocked = true;
            properties.ResumePropertyChanged(false);
        }

        private void EnterSynchronizing(WFStep step, ThreadProperties properties)
        {
            properties.LockName = this.Name;
            properties.LockingStep = step.Sequence;
            properties.IsLocked = false;
        }

        private void UnlockThread(WFStep step)
        {
            WFThread thread = step.Thread;
            ThreadProperties properties = thread.Properties;

            if (!properties.IsLocked)
                throw new WorkflowException("The specified thread is not locked.");

            ExitSynchronizing(properties);

        }

        internal void ExitSynchronizing(WFStep step)
        {
            WFThread thread = step.Thread;
            ExitSynchronizing(thread);

        }

        internal void ExitSynchronizing(WFThread thread)
        {
            ThreadProperties properties = thread.Properties;

            if (properties.IsLocked)
                throw new WorkflowException("The specified thread is locked and cannot exit synchronizing.");

            ExitSynchronizing(properties);
        }

        private void ExitSynchronizing(ThreadProperties properties)
        {
            if (properties.LockName != this.Name)// || !properties.IsLocked)
                throw new WorkflowException("The specified thread is not sychronizing with this lock.");

            //if (properties.LockingStep != step.Sequence)
            //    throw new WorkflowException("The thread was not locked by the specified step, and cannot be unlocked.");

            properties.SuspendPropertyChanged();
            properties.LockName = null;
            properties.LockingStep = 0;
            properties.IsLocked = false;
            properties.ResumePropertyChanged();
        }

        private void EnterSynchronizing(WFStep step)
        {
            WFThread thread = step.Thread;
            var properties = thread.Properties;

            if (properties.IsSynchronizing)
                throw new WorkflowException("Cannot synchronize thread because it was locked already.");

            properties.SuspendPropertyChanged();
            EnterSynchronizing(step, properties);
            properties.ResumePropertyChanged(false);
        }

        internal void Wait(WFStep step)
        {
            if (ShouldWait(step.Thread.Process))
                EnterSynchronizing(step);
        }

        internal static bool IsThreadSychronizing(WFThread thread)
        {
            return IsThreadSychronizing(thread, null);
        }

        internal static bool IsThreadSychronizing(WFThread thread, ThreadLock tlock)
        {
            var p = thread.Properties;
            bool locked = p.IsSynchronizing;

            if (locked && tlock != null)
            {
                locked = p.LockName == tlock.Name;
            }

            return locked;
        }

        internal static bool IsThreadLocked(WFThread thread, ThreadLock tlock)
        {
            bool l = IsThreadSychronizing(thread, tlock);
            if (l) l = thread.Properties.IsLocked;
            return l;
        }
    }

    public class ThreadLockCollection : OwnedCollection<ThreadLock, Workflow>
    {
        public ThreadLockCollection(Workflow owner)
            : base(owner)
        {

        }

        protected override void SetItemOwner(ThreadLock item, Workflow parent)
        {
            item.Workflow = parent;
        }

        protected override Workflow GetItemOwner(ThreadLock item)
        {
            return item.Workflow;
        }

        internal ThreadLock GetLock(string name)
        {
            ThreadLock tl = this.SingleOrDefault(o => o.Name == name);
            if (tl == null)
                throw new ArgumentException("Thread lock " + name + " not defined.");

            return tl;
        }
    }

}
